package com.example.synchost5.store.local;

import com.example.synchost5.entity.DatabaseStructure;
import com.example.synchost5.store.DataStore;
import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class LocalStore implements DataStore, SmartLifecycle {

    private static final Logger logger = LoggerFactory.getLogger(LocalStore.class);

    private static final String FILE_PATH = "./db/";
    private static final String FILE_NAME = "db";
    private static final String FILE_NAME_EXTENSION = ".json";
    private static final String FILE_FULL_PATH = FILE_PATH + FILE_NAME + FILE_NAME_EXTENSION;

    private static boolean isRunning = false;
    private DatabaseStructure data = null;

    @Override
    public String getStaticPart(String configName) {
        return data.staticPart.get(configName);
    }

    @Override
    public Set<String> getDynamicPart(String configName) {
        return data.dynamicPart.get(configName);
    }

    @Override
    public String getIP(String domain) {
        return data.addressPool.get(domain);
    }

    @Override
    public boolean hasConfigName(String configName) {
        return data.staticPart.containsKey(configName);
    }

    @Override
    public Map<String, String> allIPData() {
        return data.addressPool;
    }

    @Override
    public boolean saveConfig(String configName, String staticPart, Set<String> dynamicPart) {
        data.staticPart.put(configName, staticPart);
        data.dynamicPart.put(configName, dynamicPart);
        for (String domain : dynamicPart) {
            data.addressPool.putIfAbsent(domain, "");
        }
        return true;
    }

    @Override
    public boolean updateIP(Map<String, String> ipMap) {
        for (Map.Entry<String, String> entry : ipMap.entrySet()) {
            String domain = entry.getKey();
            String ip = entry.getValue();
            data.addressPool.put(domain, ip);
        }
        return true;
    }

    @Override
    public boolean updateIP(String domain, String ip) {
        data.addressPool.put(domain, ip);
        return true;
    }

    /**
     * 初始化: 将本地数据文件加载到内存
     */
    private void startupStore() {
        File file = new File(FILE_FULL_PATH);
        if (file.exists()) {
            try (FileReader reader = new FileReader(file)) {
                Gson gson = new Gson();
                data = gson.fromJson(reader, DatabaseStructure.class);
            } catch (IOException e) {
                logger.error("IO Exception: ", e);
            }
        } else {
            data = new DatabaseStructure(new ConcurrentHashMap<>(), new ConcurrentHashMap<>(), new ConcurrentHashMap<>());
        }
        logDataInfo();
    }

    /**
     * 持久化: 将内存中的结果保存成本地数据文件
     */
    private void shutdownStore() {
        File path = new File(FILE_PATH);
        if (!path.exists()) {
            boolean hasMade = path.mkdirs();
            if (!hasMade) {
                logger.error("mkdir [" + FILE_PATH + "] failed!");
                return;
            }
        }
        File old = new File(FILE_FULL_PATH);
        File target;
        if (old.exists()) {
            File backup = new File(FILE_PATH + FILE_NAME + "_backup_" + System.currentTimeMillis() + FILE_NAME_EXTENSION);
            if (old.renameTo(backup)) {
                target = new File(FILE_FULL_PATH);
            } else {
                target = new File(FILE_PATH + FILE_NAME + System.currentTimeMillis() + FILE_NAME_EXTENSION);
            }
        } else {
            target = new File(FILE_FULL_PATH);
        }
        try (FileWriter fileWriter = new FileWriter(target)) {
            fileWriter.write(new Gson().toJson(data));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void logDataInfo() {
        logger.info("Configs: " + data.staticPart.size());
        logger.info("IPs: " + data.addressPool.size());
    }

    @Override
    public void start() {
        startupStore();
        isRunning = true;
        logger.info("init store complete");
    }

    @Override
    public void stop() {
        shutdownStore();
        isRunning = false;
        logger.info("persist store complete");
    }

    @Override
    public boolean isRunning() {
        return isRunning;
    }


}
